using System;
using System.Collections.Generic;
using System.Text;
using MacomberMap.Common.Database_Interface.CIM;
using System.IO;
using System.Data.SQLite;
using MacomberMap.Common.Database_Interface.CIM.Components;
using System.Collections;
using System.Windows.Forms;
using System.Threading;
using MacomberMap.Common.Components;

namespace MacomberMap.Loader.Export
{
    /// <summary>
    /// This class creates a SQLite database and creates a CIM ID of elements
    /// </summary>
    public class MM_Exporter_SQLite : MM_Exporter
    {
        #region Variable declarations

        /// <summary>Return the current progress</summary>
        public override float Progress
        {
            get { return Current / Total; }
        }

        /// <summary>The current step of export</summary>
        private float Current = 0;

        /// <summary>The total number of steps for export</summary>
        private float Total;
        #endregion


        /// <summary>
        /// Initialize a new SQLite exporter
        /// </summary>
        public MM_Exporter_SQLite(ListView Parent)
            : base(Parent, "SQLite database")
        {
            Total = MM_Database_CIM.Elements.Count + MM_Database_CIM.TEIDs.Count + MM_Database_CIM.Types.Count + MM_Database_CIM.Attributes.Count + MM_Database_CIM.TypesByAttribute.Count + MM_Database_CIM.AttributesByType.Count;
        }


        /// <summary>
        /// Start the export process
        /// </summary>
        public override void ProcessExporter()
        {
            Current = 0;
            Total = 7+MM_Database_CIM.Elements.Count + MM_Database_CIM.TEIDs.Count + MM_Database_CIM.Types.Count + MM_Database_CIM.Attributes.Count + MM_Database_CIM.TypesByAttribute.Count + MM_Database_CIM.AttributesByType.Count;
            FileInfo fI = new FileInfo(MM_Database_CIM.CIMFileName);
            string DbName = fI.DirectoryName + "\\" + Path.GetFileNameWithoutExtension(fI.Name) + ".SQLite";
            if (File.Exists(DbName))
                File.Delete(DbName);

            using (SQLiteConnection sConn = new SQLiteConnection("Data Source=" + DbName))
            {
                sConn.Open();
                CreateTables(sConn);

                using (SQLiteTransaction sTrans = sConn.BeginTransaction())
                {
                    ExecuteCommand(sConn, sTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", "TEIDCount", MM_Database_CIM.TEIDs.Count);
                    ExecuteCommand(sConn, sTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", "ElementCount", MM_Database_CIM.Elements.Count);
                    ExecuteCommand(sConn, sTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)",  "MaxMRID", MM_Database_CIM.MaxMRID);
                    UInt64 MaxTEID = 0;
                    foreach (UInt64 TEID in MM_Database_CIM.TEIDs.Keys)
                        MaxTEID = Math.Max(TEID, MaxTEID);
                    ExecuteCommand(sConn, sTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", "MaxTEID", MaxTEID);
                    ExecuteCommand(sConn, sTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", "ExportedOn", new FileInfo(MM_Database_CIM.CIMFileName).LastWriteTime);                        
                    ExecuteCommand(sConn, sTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", "ModelClass", "");
                    ExecuteCommand(sConn, sTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", "FullClass", "");
                        
                    foreach (KeyValuePair<String, Int32> kvp in MM_Database_CIM.Types)
                        ExecuteCommand(sConn, sTrans, "INSERT INTO CIM_Types ([ID],[Name]) VALUES (:0,:1)", kvp.Value, kvp.Key);
                    foreach (KeyValuePair<String, Int32> kvp in MM_Database_CIM.Attributes)
                        ExecuteCommand(sConn, sTrans, "INSERT INTO CIM_Attributes ([ID],[Name]) VALUES (:0,:1)", kvp.Value, kvp.Key);
                    foreach (KeyValuePair<UInt16, List<Byte>> kvp in MM_Database_CIM.TypesByAttribute)
                        ExecuteCommand(sConn, sTrans, "INSERT INTO CIM_TypesByAttribute ([ID],[Attributes]) VALUES (:0,:1)", kvp.Key, kvp.Value);
                    foreach (KeyValuePair<Byte, List<UInt16>> kvp in MM_Database_CIM.AttributesByType)
                        ExecuteCommand(sConn, sTrans, "INSERT INTO CIM_AttributesByType ([ID],[Types]) VALUES (:0,:1)", kvp.Key, kvp.Value);

                    foreach (KeyValuePair<byte, List<byte>> kvp in MM_Database_CIM.TypeFromTypes)
                    {
                        List<Byte> InBytes;
                        if (!MM_Database_CIM.TypeToTypes.TryGetValue(kvp.Key, out InBytes))
                            InBytes = new List<byte>(0);
                        ExecuteCommand(sConn, sTrans, "INSERT INTO CIM_TypeLinkages ([ID], [InLinks], [OutLinks]) VALUES (:0, :1, :2)", kvp.Key, kvp.Value, InBytes);
                    }
                    sTrans.Commit();
                }

                using (SQLiteTransaction sTrans = sConn.BeginTransaction())
                {
                    foreach (KeyValuePair<UInt64, CIM_Element> kvp in MM_Database_CIM.TEIDs)
                        ExecuteCommand(sConn, sTrans, "INSERT INTO CIM_TEIDs ([TEID], [rdfID], [ID]) VALUES (:0, :1, :2)", kvp.Key, kvp.Value.rdfID, kvp.Value.Index);
                    sTrans.Commit();
                }

                using (SQLiteTransaction sTrans = sConn.BeginTransaction())
                {
                    foreach (CIM_Element Elem in MM_Database_CIM.Elements.Values)
                        ExecuteCommand(sConn, sTrans, "INSERT INTO CIM_Elements ([ID], [rdfID], [Type], [Start], [Length], [Name], [InLinks], [OutLinks], [Checksum]) VALUES (:0, :1, :2, :3, :4, :5, :6, :7, :8)", Elem.Index, Elem.rdfID, Elem.TypeByte, Elem.Start, Elem.Length, Elem.Name, Elem.InLinks, Elem.OutLinks, Elem.CheckSum);
                    sTrans.Commit();
                }
                sConn.Close();
            }
        }

        /// <summary>
        /// Create tables for the database
        /// </summary>
        /// <param name="sConn"></param>
        private void CreateTables(SQLiteConnection sConn)
        {
            ExecuteCommand(sConn, null, "CREATE TABLE Summary ([Name] VARCHAR PRIMARY KEY, [Value] VARCHAR)");
            ExecuteCommand(sConn, null, "CREATE TABLE CIM_Types ([ID] INTEGER PRIMARY KEY, [Name] VARCHAR UNIQUE)");
            ExecuteCommand(sConn, null, "CREATE TABLE CIM_Attributes ([ID] INTEGER PRIMARY KEY, [Name] VARCHAR UNIQUE)");
            ExecuteCommand(sConn, null, "CREATE TABLE CIM_TypesByAttribute ([ID] INTEGER PRIMARY KEY, [Attributes] BLOB)");
            ExecuteCommand(sConn, null, "CREATE TABLE CIM_AttributesByType ([ID] INTEGER PRIMARY KEY, [Types] BLOB)");
            ExecuteCommand(sConn, null, "CREATE TABLE CIM_TEIDS ([TEID] INTEGER PRIMARY KEY, [ID] INTEGER UNIQUE, [rdfID] BLOB UNIQUE)");
            ExecuteCommand(sConn, null, "CREATE TABLE CIM_Elements ([ID] INTEGER PRIMARY KEY, [rdfID] BLOB UNIQUE, [TYPE] BYTE, [START] BIGINT, [LENGTH] INT, [Name] VARCHAR, [InLinks] BLOB, [OutLinks] BLOB, [Checksum] BLOB)");
            ExecuteCommand(sConn, null, "CREATE TABLE CIM_InternalElements ([ID] INTEGER PRIMARY KEY, [CIMData] BLOB)");
            ExecuteCommand(sConn, null, "CREATE TABLE CIM_TypeLinkages ([ID] INTEGER PRIMARY KEY, [InLinks] BLOB, [OutLinks] BLOB)");            
        }


        /// <summary>        
        /// Add a record to the database
        /// </summary>
        /// <param name="sConn"></param>
        /// <param name="CommandText"></param>
        /// <param name="inValues"></param>
        /// <param name="sTrans"></param>
        public Int64 ExecuteCommand(SQLiteConnection sConn, SQLiteTransaction sTrans, String CommandText, params object[] inValues)
        {
            using (SQLiteCommand sCmd = sConn.CreateCommand())
            {
                sCmd.CommandText = CommandText + "; SELECT last_insert_rowid() AS [ID]";
                sCmd.Transaction = sTrans;
                for (int a = 0; a < inValues.Length; a++)
                    if (inValues[a] is List<byte>)
                        sCmd.Parameters.AddWithValue(":" + a.ToString(), (inValues[a] as List<byte>).ToArray());
                    else if (inValues[a] is List<UInt16>)
                    {
                        List<UInt16> OutShort = inValues[a] as List<UInt16>;
                        Byte[] outBytes = new byte[OutShort.Count * 2];
                        for (int b = 0; b < OutShort.Count; b++)
                            BitConverter.GetBytes(OutShort[b]).CopyTo(outBytes, b * 2);
                        sCmd.Parameters.AddWithValue(":" + a.ToString(), outBytes);
                    }
                    else if (inValues[a] is MM_rdfID)
                    {
                        byte[] inBytes = (inValues[a] as MM_rdfID).Bytes;
                        sCmd.Parameters.AddWithValue(":" + a.ToString(), inBytes);
                    }
                    else if (inValues[a] is IEnumerable<CIM_Element>)
                    {
                        using (MemoryStream OutStream = new MemoryStream())
                        using (BinaryWriter bW = new BinaryWriter(OutStream))
                        {
                            foreach (CIM_Element Elem in (inValues[a] as IEnumerable<CIM_Element>))
                            {
                                bW.Write(Elem.TypeByte);
                                bW.Write(Elem.Index);
                            }
                            bW.Flush();
                            sCmd.Parameters.AddWithValue(":" + a.ToString(), OutStream.ToArray());
                        }
                    }

                    else if (inValues[a] is MM_rdfID[])
                    {
                        List<Byte> outBytes = new List<byte>();
                        foreach (MM_rdfID rdfID in inValues[a] as MM_rdfID[])
                            outBytes.AddRange(rdfID.Bytes);
                        sCmd.Parameters.AddWithValue(":" + a.ToString(), outBytes.ToArray());
                    }
                    else
                        sCmd.Parameters.AddWithValue(":" + a.ToString(), inValues[a]);
                Current++;
                return Convert.ToInt64(sCmd.ExecuteScalar());
            }

        }

    }
}
